package cn.jack.audioclip

import android.annotation.SuppressLint
import android.media.AudioFormat
import android.media.MediaCodec
import android.media.MediaExtractor
import android.media.MediaFormat
import android.os.Environment
import java.io.File
import java.io.FileOutputStream
import java.nio.ByteBuffer

/**
 * @创建者 Jack
 * @创建时间 2024/1/15
 * @描述
 */
object ClipHelper {
    @SuppressLint("WrongConstant")
    fun clip(musicPath: String, outPath: String, startTime: Int, endTime: Int) {
        //边界处理
        if (endTime < startTime) {
            return
        }

        //创建解封装的类
        val mediaExtractor = MediaExtractor()

        //设置数据源为音频文件路径
        mediaExtractor.setDataSource(musicPath)
        //选择音频轨道
        val audioTrack = selectTrack(mediaExtractor)

        //4.选择指定音频轨道
        mediaExtractor.selectTrack(audioTrack)
        //5.调用seekTo方法（定位到指定的起始时间），参数2：指定寻找模式的参数
        //SEEK_TO_PREVIOUS_SYNC：寻找最接近给定时间戳之前的同步帧。
        //SEEK_TO_CLOSEST_SYNC：寻找最接近给定时间戳的同步帧。
        mediaExtractor.seekTo(startTime.toLong(), MediaExtractor.SEEK_TO_CLOSEST_SYNC)
        //6.获取媒体文件中指定轨道（track）的格式信息。
        val oriAudioFormat = mediaExtractor.getTrackFormat(audioTrack)
        var maxBufferSize = 100 * 1000  //提供一个默认值
        maxBufferSize = if (oriAudioFormat.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
            oriAudioFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)
        } else {
            100 * 1000
        }

        //7.创建缓冲区
        val buffer = ByteBuffer.allocateDirect(maxBufferSize)
        //8.创建MediaCodec
        val mediaCodec = MediaCodec.createDecoderByType(oriAudioFormat.getString(MediaFormat.KEY_MIME)!!)
        //9.配置编解码器的参数
        mediaCodec.configure(oriAudioFormat, null, null, 0)

        val pcmFile = File(Environment.getExternalStorageDirectory(), "out.pcm")
        val writeChannel = FileOutputStream(pcmFile).channel

        //开始解码
        mediaCodec.start()

        //创建 BufferInfo 用于保存解码信息
        val info = MediaCodec.BufferInfo()
        var outputBufferIndex = -1
        // 循环解码输入数据
        while (true) {

            //10.如果在指定的时间没有查询出可用的ByteBuffer，就返回-1
            val decodeInputIndex = mediaCodec.dequeueInputBuffer(100000)
            if (decodeInputIndex >= 0) {
                // 获取当前样本时间
                val sampleTimeUs = mediaExtractor.sampleTime
                if (sampleTimeUs == -1L) {
                    break
                } else if (sampleTimeUs < startTime) {
                    // 若样本时间小于起始时间，跳过该样本
                    //11.不用解码   前进到下一帧
                    mediaExtractor.advance()
                    continue
                } else if (sampleTimeUs > endTime) {
                    // 若样本时间大于结束时间，跳出循环
                    break
                }
                //12.获取到压缩数据（读取样本数据到缓冲区）
                //用于读取媒体文件中当前轨道（track）的样本数据。这个方法通常与 MediaExtractor.advance 一起使用，用于逐帧遍历媒体文件。
                //参数1：byteBuf，一个 ByteBuffer 对象，用于接收读取的样本数据。读取的数据将被写入这个缓冲区。
                //参数2：offset： 缓冲区的偏移量，表示从缓冲区的哪个位置开始写入数据。
                //返回值是读取的样本数据的大小（以字节为单位），如果到达文件末尾或发生错误，则返回负值。
                info.size = mediaExtractor.readSampleData(buffer, 0)
                info.presentationTimeUs = sampleTimeUs
                info.flags = mediaExtractor.sampleFlags

                // 将 ByteBuffer 转为 byte 数组
//                val content = ByteArray(buffer.remaining())
//                //将content放入buffer中，后续代码就可以正确地content数组中的数据
//                buffer.get(content)

                //输出文件  方便查看
//                FileUtils.writeContent(content)

                //以下两行代码的作用：将原始的音频数据放入到 MediaCodec 的输入缓冲区中，从而使得 MediaCodec 能够进行解码操作。
                //这两行代码是 MediaCodec 接收输入数据的标准流程，去掉它们会导致解码器无法获取到输入数据，从而无法进行解码。如果去掉这两行代码，将无法完成音频数据的硬解码。

                //获取用于输入数据的 ByteBuffer 对象
                //获取用于将输入数据传递给 MediaCodec 进行编码或解码的缓冲区。通过获取输入缓冲区，你可以将原始数据（如视频帧或音频样本）填充到这个缓冲区中，然后将缓冲区提交给 MediaCodec 进行处理。
                val inputBuffer = mediaCodec.getInputBuffer(decodeInputIndex)//得到inputBuffer，用于存放音频数据
                //将原始的音频数据（content）放入 inputBuffer 中
//                inputBuffer!!.put(content)

                //尝试将 val content = ByteArray(buffer.remaining()) buffer.get(content) inputBuffer!!.put(content)去掉替换成下方的代码
                inputBuffer!!.put(buffer)

                //13.通知dsp芯片进行解码
                //参数1：要放入的输入缓冲区的索引
                //参数2：输入数据在输入缓冲区中的偏移量。通常，可以设置为 0，表示从输入数据的起始位置开始。
                //参数3：表示输入数据的大小，即要放入输入缓冲区的数据的字节数
                //参数4：表示输入数据的展示时间戳，以微秒为单位。对于视频来说，它通常是视频帧的显示时间；对于音频来说，它通常是音频样本的播放时间。
                //可以通过调整时间戳来实现对播放速度的控制
                //参数5：表示输入数据的标志位，用于指定一些特殊的处理要求。常见的标志位包括：
                //BUFFER_FLAG_KEY_FRAME: 表示关键帧。
                //BUFFER_FLAG_END_OF_STREAM: 表示输入流结束。
                //传入0，表示没有特殊的标志或配置，即不启用任何额外的特性。
                mediaCodec.queueInputBuffer(
                    decodeInputIndex,
                    0,
                    info.size,
                    info.presentationTimeUs,
                    info.flags
                )
                //14.释放上一帧的压缩数据
                mediaExtractor.advance()
            }
            //15.（从输出缓冲区队列中获取可用的输出缓冲区索引）获取解码后的数据，以便应用程序可以进一步处理或渲染。
            //15.1.在参数2时间内没有获取到就返回-1     dequeueOutputBuffer方法的设计有些不太一样
            //15.2.参数1：一个 MediaCodec.BufferInfo 对象，用于存储输出缓冲区的相关信息，例如数据的大小、时间戳等。
            outputBufferIndex = mediaCodec.dequeueOutputBuffer(info, 100000)
            while (outputBufferIndex >= 0) {
                //16.获取对应的输出缓冲区，并从中读取解码后的数据
                val decodeOutputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex)

                writeChannel.write(decodeOutputBuffer)

                //17.解码器的输出缓冲区中包含解码后的音视频数据。应用程序可以根据具体的需求，将这些数据用于播放、存储或其他后续处理。在处理完数据后，
                // 需要调用 releaseOutputBuffer 方法来释放输出缓冲区，使其变为可用状态供下一次使用。
                //17.1.render 参数表示是否渲染该输出缓冲区的数据。如果设置为 true，表示渲染；如果设置为 false，则不进行渲染。
                mediaCodec.releaseOutputBuffer(outputBufferIndex, false)
                outputBufferIndex = mediaCodec.dequeueOutputBuffer(info, 100000)
            }
        }
        writeChannel.close()
        mediaExtractor.release()
        mediaCodec.stop()
        mediaCodec.release()

        //将pcm转换成指定格式的数据
        PcmToWavUtil(44100, AudioFormat.CHANNEL_IN_STEREO, 2, AudioFormat.ENCODING_PCM_16BIT).pcmToWav(pcmFile.absolutePath, outPath)
    }

    private fun selectTrack(mediaExtractor: MediaExtractor): Int {
        //1.获取轨道数量
        val numTracks = mediaExtractor.trackCount
        for (i in 0 until numTracks) {
            //2.配置信息
            val format = mediaExtractor.getTrackFormat(i)
            val mime = format.getString(MediaFormat.KEY_MIME)
            //3.判断类型
            if (mime!!.startsWith("audio/")) {
                return i
            }
        }
        return -1
    }
}